
#pragma once

#include <stdexcept>
#include <cassert>
#include "types.h"
#include "helpers.h"
#include "utilities.h"

namespace tools{

    template<class U, size_t I> struct Wrapper;

    namespace wrapper_details{

    template<class T, size_t N> struct WrapperBase
    {
        template<class U, size_t I> friend struct Wrapper;
        typedef IHelper<T> HelpMe;
        typedef wrapper_details::Byte Byte;
        enum { eOFFSET = sizeof(HelpMe), eNOMINAL = N, eSIZE = eNOMINAL + eOFFSET };

    public:
        //корректно вызывает диструктор ресурса. 
        //Даже если у ресурса он не виртуальный
       ~WrapperBase();

    public:

        //является ли ресурс зарегестрированным типом
        bool IsRegistred()const;
        const ::std::type_info& GetTypeInfo()const;
        const char* GetTypeName()const;
        eTYPE GetType()const;

        //пуст ли враппер
        bool Empty()const;

        //доступ к ресурсу
        template<class U> operator U&();
        template<class U> operator const U&()const;

        //true, если враппер содержит объект указанного типа
        //при этом метод определяет фактический тип наследника, даже если враппер настроен на тип базового класса
        template<class U> bool Is()const;

        //получить данные через указатель указанного типа
        template<class U> const U* GetNoCheck()const;
        template<class U> U* GetNoCheck();

        template<class U> const U* Get()const;
        template<class U> U* Get();

        template<class U> const U& Cast()const;
        template<class U> U& Cast();

        //может ли пользователь забрать данные по указанному типу
        template<class U> bool Can()const;

        //полная очистка. Вызывает диструктор для своего ресурса
        void Clear();

        // сделает клон в куче и вернет сырые данные
        T* CloneRawData()const;

        // сделает клон, поместив его в указанный буффер
        // вернет true, размер буфера вмещает объект
        template<size_t I> bool CloneRawData(unsigned char (&_dst)[I])const;
        template<size_t I> bool CloneRawData(signed char (&_dst)[I])const;
        template<size_t I> bool CloneRawData(char (&_dst)[I])const;

    public:
        #define vGEN(CLASS_NAME, ...)             \
            MakeDetails<CLASS_NAME>(__VA_ARGS__); \
            (mAlloc) ? mPtr = new CLASS_NAME(__VA_ARGS__) : mPtr = new( mData + eOFFSET ) CLASS_NAME(__VA_ARGS__);

        #define dGEN(...) vGEN(D,__VA_ARGS__)
        #define tGEN(...) vGEN(T,__VA_ARGS__)

        void Make() { tGEN(); }
        template<class T1>                   void Make(const T1& a1) { tGEN(a1); }
        template<class T1,class T2>          void Make(const T1& a1,const T2& a2) { tGEN(a1,a2); }
        template<class T1,class T2,class T3> void Make(const T1& a1,const T2& a2,const T2& a3) { tGEN(a1,a2,a3); }

        template<class D>                             void DMake() { dGEN(); }
        template<class D, class T1>                   void DMake(const T1& a1){ dGEN(a1); }
        template<class D, class T1,class T2>          void DMake(const T1& a1,const T2& a2) { dGEN(a1,a2); }
        template<class D, class T1,class T2,class T3> void DMake(const T1& a1,const T2& a2,const T2& a3) { dGEN(a1,a2,a3); }

        #undef tGEN
        #undef dGEN
        #undef vGEN

    public:
        #define ONLY_FOR_INTEGER        \
            typename ::std::enable_if< wrapper_details::PropertyType<U>::eIS_INT,    U >::type
        #define ONLY_FOR_FLOATING       \
            typename ::std::enable_if< wrapper_details::PropertyType<U>::eIS_FLOAT,  U >::type
        #define ONLY_FOR_STRICT         \
            typename ::std::enable_if< wrapper_details::PropertyType<U>::eIS_STRICT, U >::type
        #define COMPILE_ERROR_FOR_OTHER \
            typename ::std::enable_if< wrapper_details::PropertyType<U>::eIS_OTHER,  U >::type

        template<class U> ONLY_FOR_INTEGER        As()const;
        template<class U> ONLY_FOR_FLOATING       As()const;
        template<class U> const ONLY_FOR_STRICT&  As()const;
        template<class U> ONLY_FOR_STRICT&        As();
        template<class U> COMPILE_ERROR_FOR_OTHER As()const;
    protected:
        //пустой враппер. Не содержит ресурс
        WrapperBase();

        void CheckError()const;

        const T* Resource()const { CheckError(); return mPtr; }
        T* Resource() { CheckError(); return mPtr; }

        const HelpMe& MyHelper()const;

        template<class D> void MakeDetails();
        template<class U> void PlaceNewObject(const U& src);
    private:

        #define PLACE_IN_HEAP  \
            typename ::std::enable_if< (sizeof(U)>eNOMINAL), U >::type* = nullptr
        #define PLACE_IN_STACK \
            typename ::std::enable_if< !(sizeof(U)>eNOMINAL), U>::type* = nullptr

        template<class U> void PlaceObject(const U& src, PLACE_IN_HEAP );
        template<class U> void PlaceObject(const U& src, PLACE_IN_STACK);
        #undef PLACE_IN_STACK
        #undef PLACE_IN_HEAP

        T* CloneObjectIntoHeap()const;
        T* CloneObjectIntoStack(Byte* dst)const; 

        void ThrowIfFalse(const bool valid)const;

        //приведет тип ресурса к типу U
        //U и ресурс должны быть интегральными, иначе ошибка компиляции,
        //либо будет брошено исключение
        template<class U> U CastIntegralType()const;

        static size_t GetSizeBufer() { return eNOMINAL; }

    protected:
        T* mPtr; 
        Byte mData[eSIZE]; 
        bool mAlloc;
    };
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        WrapperBase<T,N>::~WrapperBase(){ Clear(); }

    template<class T, size_t N>
        WrapperBase<T,N>::WrapperBase()
            :mPtr(nullptr)
            ,mAlloc(false)
        {}
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        void WrapperBase<T,N>::CheckError()const
        {
            const bool ERROR_WRAPPER_IS_EMPTY = !mPtr;
            //assert(!ERROR_WRAPPER_IS_EMPTY);
            if(ERROR_WRAPPER_IS_EMPTY)
                throw ::std::logic_error("ERROR_WRAPPER_IS_EMPTY");
        }

//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        void WrapperBase<T,N>::ThrowIfFalse(const bool valid)const
        {
            const bool ERROR_INVALID_SIZE_OF_TYPE = !valid;
            assert(!ERROR_INVALID_SIZE_OF_TYPE);
            if(ERROR_INVALID_SIZE_OF_TYPE)
                throw std::logic_error("ERROR_INVALID_SIZE_OF_TYPE");
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        const typename WrapperBase<T, N>::HelpMe& WrapperBase<T, N>::MyHelper()const
        {
            const HelpMe* bld = reinterpret_cast<const HelpMe*>(mData);
            return *bld; 
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>    
        template<class D> void WrapperBase<T,N>::MakeDetails()
        {
            static_assert(std::is_convertible<D*,T*>::value || ::std::is_same<T,void>::value, "TYPES ARE NOT CONVERTED");

            Clear();
            new( mData ) wrapper_details::Helper<D, T>;
            mAlloc = sizeof(D) > (eSIZE - eOFFSET);
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        template<class U> bool WrapperBase<T,N>::Is()const 
        {
            return Empty()? false : MyHelper().template IsSame<U>(); 
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        bool WrapperBase<T,N>::IsRegistred()const { return MyHelper().IsRegistred(); }
    
    template<class T, size_t N>
        const ::std::type_info& WrapperBase<T,N>::GetTypeInfo()const{ return MyHelper().GetTypeInfo(); }
    
    template<class T, size_t N>
        const char* WrapperBase<T,N>::GetTypeName()const{ return MyHelper().GetTypeName(); }
    
    template<class T, size_t N>
        eTYPE WrapperBase<T,N>::GetType()const { return MyHelper().GetType(); }

//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        bool WrapperBase<T,N>::Empty()const { return !mPtr; }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        template<class U> WrapperBase<T,N>::operator U&()            { return *Get<U>(); }
        
    template<class T, size_t N>
        template<class U> WrapperBase<T,N>::operator const U&()const { return *Get<U>(); }

//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        T* WrapperBase<T,N>::CloneRawData()const
        {
            return Empty()? (T*)nullptr : MyHelper().CloneObjectIntoHeap( mData+eOFFSET);
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        template<size_t I> bool WrapperBase<T,N>::CloneRawData(unsigned char (&_dst)[I])const
        {
            typedef Byte (&Arr)[I]; auto dst = reinterpret_cast<Arr>(_dst);
            return (Empty() || I<eSIZE )? false : MyHelper().CloneObjectIntoStack(mData + eOFFSET, dst ), true;
        }
    template<class T, size_t N>
        template<size_t I> bool WrapperBase<T,N>::CloneRawData(char (&_dst)[I])const
        {
            typedef Byte (&Arr)[I]; auto dst = reinterpret_cast<Arr>(_dst);
            return (Empty() || I<eSIZE )? false : MyHelper().CloneObjectIntoStack(mData + eOFFSET, dst ), true;
        }
    template<class T, size_t N>
        template<size_t I> bool WrapperBase<T,N>::CloneRawData(signed char (&_dst)[I])const
        {
            typedef Byte (&Arr)[I]; auto dst = reinterpret_cast<Arr>(_dst);
            return (Empty() || I<eSIZE )? false : MyHelper().CloneObjectIntoStack(mData + eOFFSET, dst ), true;
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        T* WrapperBase<T,N>::CloneObjectIntoHeap()const
        {
            return MyHelper().CloneObjectIntoHeap( reinterpret_cast<Byte*>(mPtr)  ); 
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        T* WrapperBase<T,N>::CloneObjectIntoStack(Byte* dst)const 
        {
            if(mAlloc)
                return MyHelper().CloneObjectIntoStack(reinterpret_cast<Byte*>(mPtr), dst + eOFFSET );

            return MyHelper().CloneObjectIntoStack(mData + eOFFSET, dst + eOFFSET );
        }

//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        void WrapperBase<T,N>::Clear()
        {
            if( !Empty() )
                MyHelper().Deleter( reinterpret_cast<Byte*>(mPtr), mAlloc),
                mAlloc = false,
                mPtr = nullptr;
        }
//---------------------------------------------------------------------------------------------

    template<class T, size_t N> 
        template<class U> const U* WrapperBase<T,N>::Get()const
        {
            CheckError();
            const bool ERROR_INVALID_TYPE = !Is<U>();
            assert(!ERROR_INVALID_TYPE);
            if(ERROR_INVALID_TYPE)
                throw std::logic_error("ERROR_INVALID_TYPE");
            //return reinterpret_cast<const U*>(mData+eOFFSET);
            return reinterpret_cast<const U*>(mPtr);
        }

    template<class T, size_t N>
        template<class U> U* WrapperBase<T,N>::Get()
        {
            return const_cast<U*>( static_cast<const WrapperBase*>(this)->Get<U>() ); 
        }

//---------------------------------------------------------------------------------------------
    template<class T, size_t N> 
        template<class U> const U& WrapperBase<T,N>::Cast()const
        {
            CheckError();
            const bool ERROR_INVALID_TYPE = !Is<U>();
            assert(!ERROR_INVALID_TYPE);
            if(ERROR_INVALID_TYPE)
                throw std::logic_error("ERROR_INVALID_TYPE");

            const U* ptr = reinterpret_cast<const U*>(mData+eOFFSET);
            return *ptr;
        }

    template<class T, size_t N>
        template<class U> U& WrapperBase<T,N>::Cast()
        {
            return const_cast<U&>( static_cast<const WrapperBase&>(this)->Get<U>() ); 
        }

//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        template<class U> const U* WrapperBase<T,N>::GetNoCheck()const
        {
            //return reinterpret_cast<const U*>(mData+eOFFSET);
            return reinterpret_cast<const U*>(mPtr);
        }
    template<class T, size_t N>
        template<class U> U* WrapperBase<T,N>::GetNoCheck()
        {
            return const_cast<U*>( static_cast<const WrapperBase*>(this)->GetNoCheck<U>() ); 
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        template<class U> bool WrapperBase<T,N>::Can()const
        {
            typedef PropertyType<U> Property;

            auto& bld = MyHelper();

            if(Property::eIS_STRICT)
                return Is<U>();
            if(sizeof(U) < bld.GetSize())
                return false;
            if(Property::eIS_INT)
                return bld.IsInteger();
            if(Property::eIS_FLOAT)
                return bld.IsFloating() || bld.IsInteger();
            return false;
        }
//---------------------------------------------------------------------------------------------

    template<class T, size_t N> 
        template<class U> ONLY_FOR_INTEGER  WrapperBase<T,N>::As()const
        {
            const auto& bld = MyHelper();
            ThrowIfFalse( sizeof(U)>=bld.GetSize() && bld.IsInteger() );
            return CastIntegralType<U>();
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N> 
        template<class U> ONLY_FOR_FLOATING WrapperBase<T,N>::As()const
        {
            const auto& bld = MyHelper();
            const bool is_numeric = bld.IsInteger() || bld.IsFloating();
            ThrowIfFalse( sizeof(U)>=bld().GetSize() && is_numeric );
            return CastIntegralType<U>();
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N> 
        template<class U> ONLY_FOR_STRICT&  WrapperBase<T,N>::As()            { return *Get<U>(); }
    template<class T, size_t N> 
        template<class U> const ONLY_FOR_STRICT&  WrapperBase<T,N>::As()const { return *Get<U>(); }
//---------------------------------------------------------------------------------------------    
    template<class T, size_t N> 
        template<class U> COMPILE_ERROR_FOR_OTHER  WrapperBase<T,N>::As()const 
        {
            enum { eVALID = !wrapper_details::PropertyType<U>::eIS_OTHER };
            static_assert( eVALID, "THIS_TYPE_IS_NOT_SUPPORT" ); 
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        template<class U> U WrapperBase<T,N>::CastIntegralType()const
        {
            #define STATIC_CAST_NUMERIC(type)           \
            if( Is<type>() )                            \
                return  static_cast<U>( *Get<type>())

            STATIC_CAST_NUMERIC(int8_t)  ;
            STATIC_CAST_NUMERIC(int16_t) ;
            STATIC_CAST_NUMERIC(int32_t) ;
            STATIC_CAST_NUMERIC(int64_t) ;
            STATIC_CAST_NUMERIC(uint16_t);
            STATIC_CAST_NUMERIC(uint32_t);
            STATIC_CAST_NUMERIC(uint64_t);
            STATIC_CAST_NUMERIC(float)   ;
            STATIC_CAST_NUMERIC(double)  ;
            STATIC_CAST_NUMERIC(Byte)    ;
            #undef STATIC_CAST_NUMERIC
            throw ::std::runtime_error("ERROR_MUST_BE_INTEGRAL_TYPE"); 
        }
//---------------------------------------------------------------------------------------------
    template<class T, size_t N>
        template<class U> void WrapperBase<T,N>::PlaceNewObject(const U& src)
        {
            static_assert(std::is_convertible<U*,T*>::value || ::std::is_same<T,void>::value, "TYPES ARE NOT CONVERTED");

            Clear();
            new(this->mData) wrapper_details::Helper<U, T>;
            PlaceObject(src);
        }
//---------------------------------------------------------------------------------------------
    #define PLACE_IN_HEAP  \
        typename ::std::enable_if< (sizeof(U)>eNOMINAL), U >::type*
    #define PLACE_IN_STACK \
        typename ::std::enable_if< !(sizeof(U)>eNOMINAL), U>::type*

    template<class T, size_t N>
        template<class U> void WrapperBase<T,N>::PlaceObject(const U& src, PLACE_IN_HEAP )  
        {
            mPtr = new U(src); 
            mAlloc=true; 
        }
    template<class T, size_t N>
        template<class U> void WrapperBase<T,N>::PlaceObject(const U& src, PLACE_IN_STACK ) 
        {
            mPtr = new( mData + eOFFSET ) U(src); 
            mAlloc=false;  
        }
    #undef PLACE_IN_STACK
    #undef PLACE_IN_HEAP

//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------



    }//namespace wrapper_details
}//namespace tools

#undef ONLY_FOR_INTEGER        
#undef ONLY_FOR_FLOATING       
#undef ONLY_FOR_STRICT         
#undef COMPILE_ERROR_FOR_OTHER 
